#include "driver.h"

#include "portdescriptors.h"
#include "driverdata.h"

/* state machine */
int switch_to_port_delayed = -1;
int driver_connected = 0;
int	current_port = 0;
u8 portaddress[8]; 
struct UsbbdDeviceRequest request;
struct UsbbdDeviceRequest hub_request;
int comstatus = 0;

/* Complete requests */
void hub_complete_request (struct UsbbdDeviceRequest *req) { req->unused = NULL; DEBUG("[H:OK:%i:%i]\n",req->transmitted,req->returnCode); }
void dev_complete_request (struct UsbbdDeviceRequest *req) { req->unused = NULL; DEBUG("[D:OK:%i:%i]\n",req->transmitted,req->returnCode); }
void complete_request (struct UsbbdDeviceRequest *req) { req->unused = NULL; DEBUG("[I:OK:%i:%i]\n",req->transmitted,req->returnCode); }

/* Hub functions */
unsigned short intdata[] = { 2 , 0 };

void hub_interrupt_transmit() {
		int i;
		intdata[0] = '\0';
		for (i = 0; i < 6; i++) { if (localports[i].change != 0) intdata[0] |= 1 << (i+1); }
		if ( intdata[0] != 0 ) {
			DEBUG("[!] Enviando byte de interrupcion (0x%X,%d): ",intdata[0],intdata[1]);
			if ( !hub_request.unused ) {
				hub_request.data = (unsigned short *)&intdata; 
				hub_request.size = 1;
				hub_request.onComplete = &complete_request;
				hub_request.unused = &hub_request;
				hub_request.endpoint = &endp[1];
				hub_request.isControlRequest = 1;
				hub_request.next = NULL;
				hub_request.physicalAddress = &intdata;     
				DEBUG("OK\n");
				sceUsbbdReqSend(&hub_request);
			}
			else {
				DEBUG("ERR\n");
			}
		}
		else { DEBUG("[!] Nada que reportar jefe!\n"); }
}

void hub_port_changed() {
	hub_interrupt_transmit();
}

void hub_connect_port (unsigned int port) {
	
	if (port == 0 || port > 6)
		return;
	//switch_to_port (dev, 0);

	localports[port-1].status |= PORT_STAT_POWER;
	localports[port-1].status |= PORT_STAT_CONNECTION;
	localports[port-1].status |= PORT_STAT_ENABLE;
	
	//if (psfreedom_is_high_speed ())
	//	dev->hub_ports[port-1].status |= PORT_STAT_HIGH_SPEED;
	//else if (psfreedom_is_low_speed ())
		localports[port-1].status |= PORT_STAT_HIGH_SPEED;
	
	localports[port-1].change |= PORT_STAT_C_CONNECTION;
	hub_port_changed();	
}
	

/* Received request */
int usb_request(int arg1, int arg2, struct DeviceRequest *req) {
	int value = -EOPNOTSUPP;
	
	u16 w_index = req->wIndex;
	u16 w_value = req->wValue;
	u16 w_length = req->wLength;
	
	DEBUG("<< %s (%d) %d,%d ", REQUEST_STR(req->bRequest), req->bmRequestType, w_value, w_index);
	
	if ( current_port == 0 ) {
	switch (req->bRequest) {
		/* */
		case USB_REQ_GET_DESCRIPTOR:
			if ((req->bmRequestType & USB_DIR_IN) == 0) goto unknown;
			if ((req->bmRequestType & USB_TYPE_CLASS) == USB_TYPE_CLASS) {
				/* GET_HUB_DESCRIPTOR Class specific request */
				value = min(w_length, (u16) sizeof(hub_header_desc));
				request.data = (void *)&hub_header_desc;
				request.size = value;
				DEBUG(" hubdesc >> ");
			}
			else {
				switch (w_value >> 8) {
					case USB_DT_DEVICE:
						value = min(w_length, (u16) sizeof(devdesc_hi));
						request.data = (void *)&devdesc_hi;
						request.size = value;
						DEBUG(" devdesc >> ");
						break;
					case USB_DT_CONFIGURATION:
						/* Config */ request.data = (void *)&confdesc_hi; request.size = sizeof(confdesc_hi);
						/* Interf */ memcpy(request.data + request.size,(const unsigned char *)&interdesc_hi,sizeof(interdesc_hi)); request.size += sizeof(interdesc_hi);
						/* Endp   */ memcpy(request.data + request.size,(const unsigned char *)&endpdesc_hi,sizeof(endpdesc_hi)); request.size += sizeof(endpdesc_hi);			
						value = request.size;
						DEBUG(" confdesc >> ");
						if (value >= 0)
							value = min(w_length, (u16)value);
						break;
					case USB_DT_STRING:
						value = 0;
						break;
				}
			}
			break;
		/* */
		case USB_REQ_SET_FEATURE:
			if ((req->bmRequestType & USB_TYPE_CLASS) == USB_TYPE_CLASS) {
				switch (req->bmRequestType & USB_RECIP_MASK) {
					/* SET_HUB_FEATURE */
					case USB_RECIP_DEVICE:
						switch (w_value) {
							case 0: /* C_HUB_LOCAL_POWER */
							case 1: /* C_HUB_OVER_CURRENT */
								DEBUG (" SHF ");
								value = 0;
								break;
							default:
								value = -EINVAL;
								break;
						}
						break;
					case USB_RECIP_OTHER:
						/* SET_PORT_FEATURE */
						if (w_index == 0 || w_index > 6) {
							DEBUG (" SPF %d ", w_index);
							value = -EINVAL;
							break;
						}
						switch (w_value) {
							case 4: /* PORT_RESET */
								DEBUG(" SPF RST %i ",w_index);
								localports[w_index-1].change |= PORT_STAT_C_RESET;
								hub_port_changed();
								value = 0;
								break;
							case 8: /* PORT_POWER */
								DEBUG(" SPF PWR %i ",w_index);
								localports[w_index-1].status |= PORT_STAT_POWER;
								if (comstatus == 0 && w_index == 6) {
									comstatus = 1;
								}
								value = 0;
								break;
							case 0: /* PORT_CONNECTION */
							case 1: /* PORT_ENABLE */
							case 2: /* PORT_SUSPEND */
							case 3: /* PORT_OVER_CURRENT */
							case 9: /* PORT_LOW_SPEED */
							case 16: /* C_PORT_CONNECTION */
							case 17: /* C_PORT_ENABLE */
							case 18: /* C_PORT_SUSPEND */
							case 19: /* C_PORT_OVER_CURRENT */
							case 20: /* C_PORT_RESET */
							case 21: /* PORT_TEST */
							case 22: /* PORT_INDICATOR */
								DEBUG(" SPF PI %i ",w_index);
								value = 0;
								break;
							default:
								value = -EINVAL;
								break;
						}
						break;
				}
			}
			break;	
		/* */
		case USB_REQ_GET_STATUS:
			if ((req->bmRequestType & USB_TYPE_CLASS) == USB_TYPE_CLASS) {
				u16 status = 0;
				u16 change = 0;
				value = 2 * sizeof (u16);
				switch (req->bmRequestType & USB_RECIP_MASK) {
					case USB_RECIP_DEVICE:
						/* GET_HUB_STATUS */
						status = 0;
						change = 0;
						DEBUG (" GHS ");
						break;
					case USB_RECIP_OTHER:
						/* GET_PORT_STATUS */
						if (w_index == 0 || w_index > 6) {
							DEBUG (" GPS INVALID ", w_index);
							value = -EINVAL;
							break;
						}
						status = localports[w_index -1].status;
						change = localports[w_index -1].change;
						break;
					default:
						goto unknown;
				}
				if (value > 0) {
					DEBUG (" GPS %i [%d/%d] ",w_index,status, change);
					request.data = (unsigned short *)&status; request.size = sizeof(status);
					memcpy(request.data + request.size,(unsigned short *)&change,sizeof(change)); request.size += sizeof(change);
					value = request.size;
				}
			}
			break;
		/* */
		case USB_REQ_CLEAR_FEATURE:
			if ((req->bmRequestType & USB_TYPE_CLASS) == USB_TYPE_CLASS) {
				switch (req->bmRequestType & USB_RECIP_MASK) {
					/* CLEAR_HUB_FEATURE */
					case USB_RECIP_DEVICE:
						switch (w_value) {
							case 0: /* C_HUB_LOCAL_POWER */
							case 1: /* C_HUB_OVER_CURRENT */
								DEBUG(" CHF ");
								value = 0;
								break;
							default:
								value = -EINVAL;
								break;
						}
						break;
					case USB_RECIP_OTHER:
						/* CLEAR_PORT_FEATURE */
						if (w_index == 0 || w_index > 6) {
							DEBUG(" CPF: ERR %d\n", w_index);
							value = -EINVAL;
							break;
						}
						switch (w_value) {
							case 0: /* PORT_CONNECTION */
							case 1: /* PORT_ENABLE */
							case 2: /* PORT_SUSPEND */
							case 3: /* PORT_OVER_CURRENT */
							case 4: /* PORT_RESET */
							case 8: /* PORT_POWER */
							case 9: /* PORT_LOW_SPEED */
								value = 0;
								break;
							case 16: /* C_PORT_CONNECTION */
								DEBUG(" CPF CON %i ",w_index);
								localports[w_index-1].change &= ~PORT_STAT_C_CONNECTION;
								#if 0
								switch (dev->status) {
									case DEVICE1_WAIT_DISCONNECT:
										dev->status = DEVICE1_DISCONNECTED;
										//SET_TIMER (200);
										break;
									case DEVICE2_WAIT_DISCONNECT:
										dev->status = DEVICE2_DISCONNECTED;
										//SET_TIMER (170);
										break;
									case DEVICE3_WAIT_DISCONNECT:
										dev->status = DEVICE3_DISCONNECTED;
										//SET_TIMER (450);
										break;
									case DEVICE4_WAIT_DISCONNECT:
										dev->status = DEVICE4_DISCONNECTED;
										//SET_TIMER (200);
										break;
									case DEVICE5_WAIT_DISCONNECT:
										dev->status = DEVICE5_DISCONNECTED;
										//SET_TIMER (200);
										break;
									default:
										break;
								}
								#endif
								value = 0;
								break;
							case 20: /* C_PORT_RESET */
								DEBUG(" CPF RST %i ",w_index);
								localports[w_index-1].change &= ~PORT_STAT_C_RESET;
								#if 0
								switch (dev->status) {
									case DEVICE1_WAIT_READY:
										if (w_index == 1)
											dev->switch_to_port_delayed = w_index;
											DEBUG(" DLAY SWP %i ",w_index);
										break;
									case DEVICE2_WAIT_READY:
										if (w_index == 2)
											dev->switch_to_port_delayed = w_index;
										break;
									case DEVICE3_WAIT_READY:
										if (w_index == 3)
											dev->switch_to_port_delayed = w_index;
										break;
									case DEVICE4_WAIT_READY:
										if (w_index == 4)
											dev->switch_to_port_delayed = w_index;
										break;
									case DEVICE5_WAIT_READY:
										if (w_index == 5)
											dev->switch_to_port_delayed = w_index;
										break;
									case DEVICE6_WAIT_READY:
										if (w_index == 6)
											dev->switch_to_port_delayed = w_index;
										break;
									default:
										break;	
								}
								#endif
								value = 0;
								break;
							case 17: /* C_PORT_ENABLE */
							case 18: /* C_PORT_SUSPEND */
							case 19: /* C_PORT_OVER_CURRENT */
							case 21: /* PORT_TEST */
							case 22: /* PORT_INDICATOR */
								DEBUG (" CPF PI %i ",w_index);
								value = 0;
								break;
							default:
								value = -EINVAL;
								break;
						}
						break;
				}
			}
			break;
		/* */
		default:
		unknown:
			break;
	} 
	
	/* respond with data transfer before status phase? */
	if (value > 0) {
		if ( !request.unused ) {
			request.endpoint = &endp[0];
			request.isControlRequest = 0;
			request.unused = &request;
			request.endpoint = &endp[0];
			request.onComplete = &hub_complete_request;
			request.next = NULL;
			request.physicalAddress = NULL;     
			sceUsbbdReqSend(&request);
			//psfreedom_setup_complete(request);
			return value;
		}
		else { DEBUG("[IN_USE]\n"); return 0; }
	}
	else { DEBUG("[NOTHING]\n"); }
	
	}
	
	return 0;
}

/* Configure the device */
int usb_func28(int arg1, int arg2, int arg3) {
	return 0;
}

/* Attach callback, speed 1=full, 2=hi  */
int usb_attach(int speed, void *arg2, void *arg3) {
	DEBUG("[!] ATTACHED %i %x %x\n",speed,(int)arg2,(int)arg3);
	driver_connected = 1;
	return 0;
}

int usb_detach(int arg1, int arg2, int arg3) {
	DEBUG("[!] DETACHED %i %i %i\n",arg1,arg2,arg3);
	driver_connected = 0;
	return 0;
}


/* USB start function */
struct UsbData usbdata[2];
int start_func(int size, void *p) {
	/* Fill in the descriptor tables */
	memset(usbdata, 0, sizeof(usbdata));

	memcpy(usbdata[0].devdesc, &devdesc_hi, sizeof(devdesc_hi));
	usbdata[0].config.pconfdesc = &usbdata[0].confdesc;
	usbdata[0].config.pinterfaces = &usbdata[0].interfaces;
	usbdata[0].config.pinterdesc = &usbdata[0].interdesc;
	usbdata[0].config.pendp = usbdata[0].endp;
	memcpy(usbdata[0].confdesc.desc, &confdesc_hi,  sizeof(confdesc_hi));
	usbdata[0].confdesc.pinterfaces = &usbdata[0].interfaces;
	usbdata[0].interfaces.pinterdesc[0] = &usbdata[0].interdesc;
	usbdata[0].interfaces.intcount = 1;
	memcpy(usbdata[0].interdesc.desc, &interdesc_hi, sizeof(interdesc_hi));
	usbdata[0].interdesc.pendp = usbdata[0].endp;
	memcpy(usbdata[0].endp[0].desc, &endpdesc_hi[0], sizeof(endpdesc_hi[0]));
	memcpy(usbdata[0].endp[1].desc, &endpdesc_hi[1], sizeof(endpdesc_hi[1]));
	memcpy(usbdata[0].endp[2].desc, &endpdesc_hi[2], sizeof(endpdesc_hi[2]));

	PS3HubDriver.devp = usbdata[0].devdesc;
	PS3HubDriver.confp = &usbdata[0].config;

	/* USB ATTACHED LED */ driver_connected = 2;
	return 0;
}

/* USB stop function */
int stop_func(int size, void *p) {
	return 0;
}
/* Configure */
void usb_configure (int usb_version, int desc_count, struct InterfaceSettings *settings) {	
	#if 0
	if (settings->descriptors->bInterfaceClass == USB_CLASS_HUB){
		printf("Configurado modo HUB\n\n");
	} 
	else printf("Error USB Driver bInterfaceClass: %d\n",	settings->descriptors->bInterfaceClass);
	#endif
}

/* Subroutine at 0x6BB0 */
int usb_interrupt_handler (void) {
  return 0;
}

/* USB host driver */
struct UsbDriver PS3HubDriver = {
	DRIVER_NAME,
	4,
	endp,
	&intp,
	NULL, NULL, NULL, NULL,
	(struct StringDescriptor *)&hub_header_desc,
	usb_request, usb_func28, usb_attach, usb_detach,
	usb_configure,
	start_func,
	stop_func,
	NULL
};